URLconf 技巧
流线型化(Streamlining)函数导入
看下这个 URLconf,它是建立在第三章的例子上:
from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime, hours_ahead
urlpatterns = patterns('',
(r'^hello/$', hello),
(r'^time/$', current_datetime),
(r'^time/plus/(\d{1,2})/$', hours_ahead),
)
正如第三章中所解释的,在 URLconf
中的每一个入口包括了它所关联的视图函数,直接传入了一个函数对象。 这就意味着需要在模块开始处导入视图函数。但随着Django应用变得复杂,它的 URLconf
也在增长,并且维护这些导入可能使得管理变麻烦。(对每个新的view函数,你不得不记住要导入它,并且采用这种方法会使导入语句将变得相当长。)可以通过导入 views
模块本身来避免这个麻烦。 下面例子的URLconf
与前一个等价:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^hello/$', views.hello ),
(r'^time/$', views.current_datetime ),
(r'^time/plus/(d{1,2})/$', views.hours_ahead ),
)
Django 还提供了另一种方法可以在 URLconf 中为某个特别的模式指定视图函数:你可以传入一个包含模块名和函数名的字符串,而不是函数对象本身。继续示例:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^hello/$', 'mysite.views.hello' ),
(r'^time/$', 'mysite.views.current_datetime' ),
(r'^time/plus/(d{1,2})/$', 'mysite.views.hours_ahead' ),
)
(注意视图名前后的引号。应该使用带引号的'mysite.views.current_datetime'
而不是 mysite.views.current_datetime
。)
使用这个技术,就不必导入视图函数了;Django会在第一次需要它时根据字符串所描述的视图函数的名字和路径,导入合适的视图函数。
当使用字符串技术时,你可以采用更简化的方式:提取出一个公共视图前缀。在我们的URLconf例子中,每个视图字符串的开始部分都是'mysite.views'
,造成重复输入。我们可以把公共的前缀提取出来,作为第一个参数传给patterns()
函数:
from django.conf.urls.defaults import *
urlpatterns = patterns('mysite.views' ,
(r'^hello/$', 'hello' ),
(r'^time/$', 'current_datetime' ),
(r'^time/plus/(d{1,2})/$', 'hours_ahead' ),
)
注意既不要在前缀后面跟着一个点号("."
),也不要在视图字符串前面放一个点号。 Django 会自动处理它们。
牢记这两种方法,哪种更好一些呢?这取决于你的个人编码习惯和需要。
字符串方法的好处如下:
- 更紧凑,因为不需要你导入视图函数。
- 如果你的视图函数存在于几个不同的Python模块的话,它可以使得URLconf更易读和管理。
函数对象方法的好处如下:
- 更容易对视图函数进行包装(wrap)。参见本章后面的《包装视图函数》一节。
- 更 Pythonic,就是说,更符合Python的传统,如把函数当成对象传递。
两个方法都是有效的,甚至你可以在同一个 URLconf 中混用它们。决定权在你。
使用多个视图前缀
在实践中,如果你使用字符串技术,特别是当你的 URLconf 中没有一个公共前缀时,你最终可能混合视图。然而,你仍然可以利用视图前缀的简便方式来减少重复。只要增加多个 patterns()
对象,象这样:
旧的:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^hello/$', 'mysite.views.hello'),
(r'^time/$', 'mysite.views.current_datetime'),
(r'^time/plus/(\d{1,2})/$', 'mysite.views.hours_ahead'),
(r'^tag/(\w+)/$', 'weblog.views.tag'),
)
新的:
from django.conf.urls.defaults import *
urlpatterns = patterns('mysite.views',
(r'^hello/$', 'hello'),
(r'^time/$', 'current_datetime'),
(r'^time/plus/(\d{1,2})/$', 'hours_ahead'),
)
urlpatterns += patterns('weblog.views',
(r'^tag/(\w+)/$', 'tag'),
)
整个框架关注的是存在一个名为urlpatterns
的模块级别的变量。如上例,这个变量可以动态生成。这里我们要特别说明一下,patterns()
返回的对象是可相加的,这个特性可能是大家没有想到的。
调试模式中的特例
说到动态构建 urlpatterns
,你可能想利用这一技术,在 Django 的调试模式下修改 URLconf的行为。为了做到这一点,只要在运行时检查 DEBUG
配置项的值即可,如:
from django.conf import settings
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^$', views.homepage),
(r'^(\d{4})/([a-z]{3})/$', views.archive_month),
)
if settings.DEBUG:
urlpatterns += patterns('',
(r'^debuginfo/$', views.debug),
)
在这个例子中,URL链接/debuginfo/
只在你的 DEBUG
配置项设为 True
时才有效。
使用命名组
在目前为止的所有 URLconf例子中,我们使用简单的无命名正则表达式组,即,在我们想要捕获的URL部分上加上小括号,Django会将捕获的文本作为位置参数传递给视图函数。在更高级的用法中,还可以使用命名正则表达式组来捕获URL,并且将其作为关键字参数传给视图。
关键字参数对比位置参数
一个 Python 函数可以使用关键字参数或位置参数来调用,在某些情况下,可以同时进行使用。在关键字参数调用中,你要指定参数的名字和传入的值。 在位置参数调用中,你只需传入参数,不需要明确指明哪个参数与哪个值对应,它们的对应关系隐含在参数的顺序中。
例如,考虑这个简单的函数:
def sell(item, price, quantity):
print "Selling %s unit(s) of %s at %s" % (quantity, item, price)
为了使用位置参数来调用它,你要按照在函数定义中的顺序来指定参数。
sell('Socks', '$2.50', 6)
为了使用关键字参数来调用它,你要指定参数名和值。 下面的语句是等价的:
sell(item='Socks', price='$2.50', quantity=6)
sell(item='Socks', quantity=6, price='$2.50')
sell(price='$2.50', item='Socks', quantity=6)
sell(price='$2.50', quantity=6, item='Socks')
sell(quantity=6, item='Socks', price='$2.50')
sell(quantity=6, price='$2.50', item='Socks')
最后,你可以混合关键字和位置参数,只要所有的位置参数列在关键字参数之前。 下面的语句与前面的例子是等价:
sell('Socks', '$2.50', quantity=6)
sell('Socks', price='$2.50', quantity=6)
sell('Socks', quantity=6, price='$2.50')
在 Python 正则表达式中,命名的正则表达式组的语法是(?P<name>pattern
) ,这里 name
是组的名字,而 pattern
是匹配的某个模式。
下面是一个使用无名组的 URLconf 的例子:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^articles/(\d{4})/$', views.year_archive),
(r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
)
下面是相同的 URLconf,使用命名组进行了重写:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^articles/(?P<year>\d{4})/$', views.year_archive),
(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
)
这段代码和前面的功能完全一样,只有一个细微的差别:取的值是以关键字参数的方式而不是以位置参数的方式传递给视图函数的。
例如,如果不带命名组,请求/articles/2006/03/
将会等同于这样的函数调用:
month_archive(request, '2006', '03')
而带命名组,同样的请求就会变成这样的函数调用:
month_archive(request, year='2006', month='03')
使用命名组可以让你的URLconfs更加清晰,减少搞混参数次序的潜在BUG,还可以让你在函数定义中对参数重新排序。接着上面这个例子,如果我们想修改URL把月份放到 年份的 前面,而不使用命名组的话,我们就不得不去修改视图 month_archive
的参数次序。如果我们使用命名组的话,修改URL里提取参数的次序对视图没有影响。
当然,命名组的代价就是失去了简洁性:一些开发者觉得命名组的语法丑陋和显得冗余。命名组的另一个好处就是可读性强。
理解匹配/分组算法
需要注意的是如果在URLconf中使用命名组,那么命名组和非命名组是不能同时存在于同一个URLconf的模式中的。如果你这样做,Django不会抛出任何错误,但你可能会发现你的URL并没有像你预想的那样匹配正确。具体地,以下是URLconf解释器有关正则表达式中命名组和非命名组所遵循的算法:
- 如果有任何命名的组,Django会忽略非命名组而直接使用命名组。
- 否则,Django会把所有非命名组以位置参数的形式传递。
- 在以上的两种情况,Django同时会以关键字参数的方式传递一些额外参数。 更具体的信息可参考下一节。
传递额外的参数到视图函数中
有时你会发现你写的视图函数是十分类似的,只有一点点的不同。 比如说,你有两个视图,它们的内容是一致的,除了它们所用的模板不太一样:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^foo/$', views.foo_view),
(r'^bar/$', views.bar_view),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foo_view(request):
m_list = MyModel.objects.filter(is_new=True)
return render_to_response('template1.html', {'m_list': m_list})
def bar_view(request):
m_list = MyModel.objects.filter(is_new=True)
return render_to_response('template2.html', {'m_list': m_list})
我们在这代码里面做了重复的工作,不够简练。 起初你可能会想,通过对两个URL都使用同样的视图,在URL中使用括号捕捉请求,然后在视图中检查并决定使用哪个模板来去除代码的冗余,就像这样:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^(foo)/$', views.foobar_view),
(r'^(bar)/$', views.foobar_view),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foobar_view(request, url):
m_list = MyModel.objects.filter(is_new=True)
if url == 'foo':
template_name = 'template1.html'
elif url == 'bar':
template_name = 'template2.html'
return render_to_response(template_name, {'m_list': m_list})
这种解决方案的问题还是老缺点,就是把你的URL耦合进你的代码里面了。如果你打算把/foo/
改成/fooey/
的话,那么你就得记住要去改变视图里面的代码。
对一个可选URL配置参数的优雅解决方法:URLconf里面的每一个模式都可以包含第三个数据:一个关键字参数的字典:
有了这个概念以后,我们就可以把我们现在的例子改写成这样:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}),
(r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foobar_view(request, template_name):
m_list = MyModel.objects.filter(is_new=True)
return render_to_response(template_name, {'m_list': m_list})
如你所见,这个例子中,URLconf指定了 template_name
。 而视图函数会把它当成另一个参数。
这种使用额外的URLconf参数的技术以最小的代价给你提供了向视图函数传递额外信息的一个好方法。 正因如此,这技术已被很多Django的捆绑应用使用,其中以我们将在第11章讨论的通用视图系统最为明显。
下面的几节里面有一些关于你可以怎样把额外URLconf参数技术应用到你自己的工程的建议。
{$ activeFileHint $}